package com.hup.vd.m3u8;

import com.hup.utils.commons.ByteUtil;
import com.hup.vd.m3u8.M3u8Util.M3u8DataException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Base64;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author hugan
 * @date 2025/10/4
 */
@Slf4j
public class M3u8DecryptUtil {

    private static final Pattern URI_PATTERN = Pattern.compile("(?i)URI=\"([^\"]*)\"");
    private static final Pattern IV_PATTERN = Pattern.compile("(?i)IV=([^,]+)");

    public static void parseDecryptKey(M3u8Data res, String m3u8Url, RestTemplate restTemplate, Consumer<HttpHeaders> headersSetter) throws M3u8DataException {
        if (res.getDecryptMethod() == null) return;//无需解密

        //m3u8Url = "https://xxx/index.m3u8";//test
        if (m3u8Url == null) {
            //log.info("解析[本地m3u8文件], 不能处理加密字段");
            return;
        }

        String decryptMethod = res.getDecryptMethod();
        if (decryptMethod.startsWith("METHOD=AES-128")) {
            /*
            提取参数
            METHOD=AES-128,URI="enc.key",IV=0x00000000000000000000000000000000
            URI格式: xx, /xx/xx , http..
             */
            Matcher uriMatcher = URI_PATTERN.matcher(decryptMethod);
            if (uriMatcher.find()) {
                String uri = uriMatcher.group(1).trim();// group(1) 是引号内的内容
                try {
                    uri = new URL(new URL(m3u8Url), uri).toString();//处理host

                    //下载文件, 转成Base64字符串
                    log.info("开始下载.encKey={}", uri);
                    HttpHeaders headers = new HttpHeaders();
                    if (headersSetter != null) headersSetter.accept(headers);
                    HttpEntity<?> requestEntity = new HttpEntity<>(headers);
                    ResponseEntity<byte[]> response = restTemplate.exchange(uri, HttpMethod.GET, requestEntity, byte[].class);
                    String key = Base64.getEncoder().encodeToString(response.getBody());

                    res.setAesKeyBase64(key);
                } catch (MalformedURLException e) {
                    throw new M3u8DataException("url解析异常:" + m3u8Url + ", " + uri);
                }
            }

            Matcher ivMatcher = IV_PATTERN.matcher(decryptMethod);
            if (ivMatcher.find()) {
                String iv = ivMatcher.group(1).trim();
                if (iv.startsWith("0x")) iv = iv.substring(2);
                res.setAesIvHex(iv);
            }
        } else {
            throw new M3u8DataException("暂不支持解密方式:" + decryptMethod);
        }
    }

    /**
     * @param aesIvHex hex字符串(32位), 没有0x前缀
     * @apiNote 未深究:批量解密,使用ffmpeg解密
     */
    public static void decryptAES128(File source, File result, String aesKeyBase64, String aesIvHex) throws M3u8DataException {
        //log.info("解密={}, {}, {}", source.getName(), aesKeyBase64, aesIvHex);//test

        try {
            byte[] tsBytes = FileUtils.readFileToByteArray(source);

            //解密
            SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(aesKeyBase64), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            if (aesIvHex == null) {
                cipher.init(Cipher.DECRYPT_MODE, secretKey);
            } else {
                cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(ByteUtil.getBytes(aesIvHex)));
            }
            byte[] decryptedBytes = cipher.doFinal(tsBytes);

            //输出
            FileUtils.writeByteArrayToFile(result, decryptedBytes);
        } catch (Exception e) {
            throw new M3u8DataException("ts解密异常:" + source.getName() + ", " + e.getMessage());
        }
    }
}
